home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue61 / Alfresco / AAHshLnP.pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  2000-08-07  |  15.9 KB  |  506 lines

  1. {*********************************************************}
  2. {* HashLinP                                              *}
  3. {* Copyright (c) Julian M Bucknall 1997                  *}
  4. {* All rights reserved.                                  *}
  5. {*********************************************************}
  6. {* Dynamic Hash Table using Linear Probing               *}
  7. {*********************************************************}
  8.  
  9. {Note: this unit is released as freeware. In other words, you are free
  10.        to use this unit in your own applications, however I retain all
  11.        copyright to the code. JMB}
  12.  
  13. unit AAHshLnP;
  14.  
  15. interface
  16.  
  17. {$IFOPT D+}
  18. {$DEFINE DebugMode}
  19. {$ENDIF}
  20.  
  21. type
  22.   TaaHashFunction = function (const S : string) : longint;
  23.     {-Function type for a hash function}
  24.   TaaDeleteString = procedure (const S : string; aObject : pointer);
  25.     {-Procedural type for a routine to free an associated object when
  26.       a hash element (ie, string) is deleted from the table}
  27.  
  28. type
  29.   TaaHashTableLinear = class
  30.     {-a hash table that uses linear probing to resolve collisions}
  31.     private
  32.       htlArray     : pointer;
  33.       htlCount     : integer;
  34.       htlDeleteStr : TaaDeleteString;
  35.       htlHashFunc  : TaaHashFunction;
  36.       htlTableSize : integer;
  37.       {$IFDEF DebugMode}
  38.       htlDebugSeeks: integer;
  39.       {$ENDIF}
  40.     protected
  41.       procedure htlAlterTableSize(aNewTableSize : integer);
  42.       procedure htlDoDeleteString(const aKey : string; aObject : pointer);
  43.       function htlFindPrim(const aKey : string; var aIndex : integer) : boolean;
  44.       function htlGetItem(aIndex : integer) : pointer;
  45.       procedure htlGrowTable;
  46.       function htlHash(const aKey : string) : integer;
  47.       procedure htlShrinkTable;
  48.  
  49.     public
  50.       constructor Create(aTableSize : integer;
  51.                          aHashFunc  : TaaHashFunction);
  52.         {-constructor to create a hash table that can hold aTableSize
  53.           elements and that uses aHashFunc to hash strings}
  54.       destructor Destroy; override;
  55.         {-destructor to destroy the hash table}
  56.  
  57.       procedure Delete(const aKey : string);
  58.         {-delete the element defined by aKey; an exception is raised
  59.           if the string is not found}
  60.       procedure Empty;
  61.         {-delete all elements in the hash table and reset it to empty}
  62.       function Find(const aKey : string; var aObject : pointer) : boolean;
  63.         {-find the element defined by aKey; return true and the
  64.           associated object if the string is found, otherwise false}
  65.       procedure Insert(const aKey : string; aObject : pointer);
  66.         {-insert a new element defined by aKey with its associated
  67.           object aObject; an exception is raised if the string is
  68.           already present}
  69.  
  70.       property Count : integer read htlCount;
  71.         {-current number of elements in the hash table}
  72.       property Items[aIndex : integer] : pointer
  73.          read htlGetItem; default;
  74.         {-the items in the hash table}
  75.       property TableSize : integer read htlTableSize;
  76.         {-maximum number of elements in the hash table}
  77.       property OnDeleteString : TaaDeleteString
  78.          read htlDeleteStr write htlDeleteStr;
  79.         {-routine to delete an associated object when the string is
  80.           deleted}
  81.  
  82.       {$IFDEF DebugMode}
  83.       procedure debugPrint(aFileName : string; aDetailed : boolean);
  84.       {$ENDIF}
  85.   end;
  86.  
  87. function AAELFHash(const S : string) : longint;
  88.  
  89. implementation
  90.  
  91. uses
  92.   SysUtils;
  93.  
  94. {===Hash function====================================================}
  95. function AAELFHash(const S : string) : longint;
  96. var
  97.   G : longint;
  98.   i : integer;
  99. begin
  100.   Result := 0;
  101.   for i := 1 to length(S) do begin
  102.     Result := (Result shl 4) + ord(S[i]);
  103.     G := Result and $F0000000;
  104.     if (G <> 0) then
  105.       Result := Result xor (G shr 24);
  106.     Result := Result and (not G);
  107.   end;
  108. end;
  109. {====================================================================}
  110.  
  111. type
  112.   THashElementState = (hesEmpty, hesDeleted, hesInUse);
  113.  
  114.   THashElement = packed record
  115.     {$IFDEF Windows}
  116.     heString : PString;
  117.     {$ELSE}
  118.     heString : string;
  119.     {$ENDIF}
  120.     heObject : pointer;
  121.     heState  : THashElementState;
  122.     heFiller : array [0..2] of byte;
  123.   end;
  124.  
  125.   PHashElementArray = ^THashElementArray;
  126.   THashElementArray =
  127.      array [0..pred(MaxInt div sizeof(THashElement))] of THashElement;
  128.  
  129.  
  130. {===Helper routines==================================================}
  131. procedure RaiseException(const S : string);
  132. begin
  133.   raise Exception.Create(S);
  134. end;
  135. {--------}
  136. function GetClosestPrime(N : integer) : integer;
  137. {$I AAPrimes.inc}
  138. const
  139.   Forever = true;
  140. var
  141.   L, R, M : integer;
  142.   RootN   : integer;
  143.   IsPrime : boolean;
  144.   DivisorIndex : integer;
  145. begin
  146.   {treat 2 as a special case}
  147.   if (N = 2) then begin
  148.     Result := N;
  149.     Exit;
  150.   end;
  151.   {make the result equal to N, and if it's even, the next odd number}
  152.   if Odd(N) then
  153.     Result := N
  154.   else
  155.     Result := succ(N);
  156.   {if the result is within our prime number table, use binary search
  157.    to find the equal or next highest prime number}
  158.   if (Result <= MaxPrime) then begin
  159.     L := 0;
  160.     R := pred(PrimeCount);
  161.     while (L <= R) do begin
  162.       M := (L + R) div 2;
  163.       if (Result = Primes[M]) then
  164.         Exit
  165.       else if (Result < Primes[M]) then
  166.         R := pred(M)
  167.       else
  168.         L := succ(M);
  169.     end;
  170.     Result := Primes[L];
  171.     Exit;
  172.   end;
  173.   {the result is outside our prime number table range, use the
  174.    standard method for testing primality (do any of the primes up to
  175.    the root of the number divide it exactly?) and continue
  176.    incrementing the result by 2 until it is prime}
  177.   if (Result <= (MaxPrime * MaxPrime)) then begin
  178.     while Forever do begin
  179.       RootN := round(Sqrt(Result));
  180.       DivisorIndex := 1; {ignore the prime number 2}
  181.       IsPrime := true;
  182.       while (DivisorIndex < PrimeCount) and (RootN > Primes[DivisorIndex]) do begin
  183.         if ((Result div Primes[DivisorIndex]) * Primes[DivisorIndex] = Result) then begin
  184.           IsPrime := false;
  185.           Break;
  186.         end;
  187.         inc(DivisorIndex);
  188.       end;
  189.       if IsPrime then
  190.         Exit;
  191.       inc(Result, 2);
  192.     end;
  193.   end;
  194. end;
  195. {====================================================================}
  196.  
  197.  
  198. {===TaaHashTableLinear===============================================}
  199. constructor TaaHashTableLinear.Create(aTableSize : integer;
  200.                                       aHashFunc  : TaaHashFunction);
  201. begin
  202.   inherited Create;
  203.   aTableSize := GetClosestPrime(aTableSize);
  204.   GetMem(htlArray, aTableSize * sizeof(THashElement));
  205.   FillChar(htlArray^, aTableSize * sizeof(THashElement), 0);
  206.   htlTableSize := aTableSize;
  207.   htlHashFunc := aHashFunc;
  208. end;
  209. {--------}
  210. destructor TaaHashTableLinear.Destroy;
  211. begin
  212.   if (htlArray <> nil) then begin
  213.     Empty;
  214.     FreeMem(htlArray, htlTableSize * sizeof(THashElement));
  215.   end;
  216.   inherited Destroy;
  217. end;
  218. {--------}
  219. {$IFDEF DebugMode}
  220. procedure TaaHashTableLinear.debugPrint(aFileName : string; aDetailed : boolean);
  221. const
  222.   StateStrs : array [THashElementState] of string[9] =
  223.               ('<empty>  ', '<deleted>', '<in use> ');
  224. var
  225.   Inx        : integer;
  226.   discardInx : integer;
  227.   TotSeeks   : integer;
  228.   F          : System.Text;
  229. begin
  230.   System.Assign(F, aFileName);
  231.   System.Rewrite(F);
  232.   try
  233.     writeln(F, 'Hash Table (Linear Probe) Debug Print [', aFileName, ']');
  234.     writeln(F, '-------------------------------------');
  235.     if aDetailed then
  236.       writeln(F);
  237.     TotSeeks := 0;
  238.     for Inx := 0 to pred(htlTableSize) do begin
  239.       with PHashElementArray(htlArray)^[Inx] do begin
  240.         if aDetailed then
  241.           write(F, Inx:4, ': ', StateStrs[heState]);
  242.         if (heState = hesInUse) then begin
  243.           {$IFDEF Windows}
  244.           htlFindPrim(heString^, discardInx);
  245.           {$ELSE}
  246.           htlFindPrim(heString, discardInx);
  247.           {$ENDIF}
  248.           inc(TotSeeks, htlDebugSeeks);
  249.           if aDetailed then
  250.             {$IFDEF Windows}
  251.             writeln(F, '  ', heString^, '  (seekcount: ', htlDebugSeeks, ')')
  252.             {$ELSE}
  253.             writeln(F, '  ', heString, '  (seekcount: ', htlDebugSeeks, ')')
  254.             {$ENDIF}
  255.         end
  256.         else
  257.           if aDetailed then
  258.             writeln(F);
  259.       end;
  260.     end;
  261.     writeln(F);
  262.     writeln(F, 'The table has ', htlCount,
  263.                ' element(s) (of ', htlTableSize,
  264.                ') and is ', (100.0 * htlCount / htlTableSize):0:2,
  265.                '% full');
  266.     if (htlCount > 0) then
  267.       writeln(F, 'The average path length is ', (TotSeeks / htlCount):0:2, ' seeks');
  268.   finally
  269.     System.Close(F);
  270.   end;
  271. end;
  272. {$ENDIF}
  273. {--------}
  274. procedure TaaHashTableLinear.Delete(const aKey : string);
  275. var
  276.   Inx : integer;
  277. begin
  278.   if not htlFindPrim(aKey, Inx) then
  279.     RaiseException('TaaHashTableLinear.Delete: key not found');
  280.   with PHashElementArray(htlArray)^[Inx] do begin
  281.     {$IFDEF Windows}
  282.     htlDoDeleteString(heString^, heObject);
  283.     DisposeStr(heString);
  284.     {$ELSE}
  285.     htlDoDeleteString(heString, heObject);
  286.     heString := '';
  287.     {$ENDIF}
  288.     heState := hesDeleted;
  289.   end;
  290.   dec(htlCount);
  291.   {shrink the table if we're under 1/6 full}
  292.   if ((htlCount * 6) < htlTableSize) then
  293.     htlShrinkTable;
  294. end;
  295. {--------}
  296. procedure TaaHashTableLinear.Empty;
  297. var
  298.   Inx : integer;
  299. begin
  300.   for Inx := 0 to pred(htlTableSize) do begin
  301.     with PHashElementArray(htlArray)^[Inx] do begin
  302.       if (heState = hesInUse) then begin
  303.         {$IFDEF Windows}
  304.         htlDoDeleteString(heString^, heObject);
  305.         DisposeStr(heString);
  306.         {$ELSE}
  307.         htlDoDeleteString(heString, heObject);
  308.         heString := '';
  309.         {$ENDIF}
  310.       end;
  311.       heState := hesEmpty;
  312.     end;
  313.   end;
  314.   htlCount := 0;
  315. end;
  316. {--------}
  317. function TaaHashTableLinear.Find(const aKey : string; var aObject : pointer) : boolean;
  318. var
  319.   Inx : integer;
  320. begin
  321.   if htlFindPrim(aKey, Inx) then begin
  322.     Result := true;
  323.     aObject := PHashElementArray(htlArray)^[Inx].heObject;
  324.   end
  325.   else begin
  326.     Result := false;
  327.     aObject := nil;
  328.   end;
  329. end;
  330. {--------}
  331. procedure TaaHashTableLinear.htlAlterTableSize(aNewTableSize : integer);
  332. var
  333.   Inx          : integer;
  334.   OldTableSize : integer;
  335.   NewArray     : PHashElementArray;
  336.   OldArray     : PHashElementArray;
  337. begin
  338.   {allocate a new array}
  339.   GetMem(NewArray, aNewTableSize * sizeof(THashElement));
  340.   FillChar(NewArray^, aNewTableSize * sizeof(THashElement), 0);
  341.   {save the old array and element count and then set the object
  342.    fields to the new values}
  343.   OldArray := PHashElementArray(htlArray);
  344.   OldTableSize := htlTableSize;
  345.   htlArray := NewArray;
  346.   htlTableSize := aNewTableSize;
  347.   htlCount := 0;
  348.   {read through the old array and transfer over the strings/objects}
  349.   for Inx := 0 to pred(OldTableSize) do begin
  350.     with OldArray^[Inx] do begin
  351.       if (heState = hesInUse) then begin
  352.         {$IFDEF Windows}
  353.         Insert(heString^, heObject);
  354.         DisposeStr(heString);
  355.         {$ELSE}
  356.         Insert(heString, heObject);
  357.         heString := '';
  358.         {$ENDIF}
  359.       end;
  360.     end;
  361.   end;
  362.   {finally free the old array}
  363.   FreeMem(OldArray, OldTableSize * sizeof(THashElement));
  364. end;
  365. {--------}
  366. procedure TaaHashTableLinear.htlDoDeleteString(const aKey : string; aObject : pointer);
  367. begin
  368.   if Assigned(htlDeleteStr) then
  369.     htlDeleteStr(aKey, aObject);
  370. end;
  371. {--------}
  372. function TaaHashTableLinear.htlFindPrim(const aKey : string; var aIndex : integer) : boolean;
  373. var
  374.   FirstDeleted : integer;
  375.   KeyHash      : integer;
  376.   FirstKeyHash : integer;
  377. begin
  378.   {assume we'll fail}
  379.   Result := false;
  380.   {we may need to make note of the first deleted element we find, so
  381.    set the variable to some impossible value so that we know whether
  382.    we found one yet}
  383.   FirstDeleted := -1;
  384.   {calculate the hash for the string, make a note of it so we can find
  385.    out when (if) we wrap around the table completely}
  386.   KeyHash := htlHash(aKey);
  387.   FirstKeyHash := KeyHash;
  388.   {$IFDEF DebugMode}
  389.   htlDebugSeeks := 1;
  390.   {$ENDIF}
  391.   {do forever - we'll be exiting out of the loop when needed}
  392.   while true do begin
  393.     {with the current element...}
  394.     with PHashElementArray(htlArray)^[KeyHash] do
  395.       case heState of
  396.         hesEmpty   : begin
  397.                        {the state is 'empty', we must stop the linear
  398.                         probe and return either this index or the
  399.                         first deleted one we encountered}
  400.                        if (FirstDeleted <> -1) then
  401.                          aIndex := FirstDeleted
  402.                        else
  403.                          aIndex := KeyHash;
  404.                        Exit;
  405.                      end;
  406.         hesDeleted : begin
  407.                        {the state is 'deleted', we must make a note of
  408.                         this index if it's the first one we found and
  409.                         continue the linear probe}
  410.                        if (FirstDeleted = -1) then
  411.                          FirstDeleted := KeyHash;
  412.                      end;
  413.         hesInUse   : begin
  414.                        {the state is 'in use', we check to see if it's
  415.                         our string, if it is, exit returning true and
  416.                         the index}
  417.                        {$IFDEF Windows}
  418.                        if (heString^ = aKey) then begin
  419.                        {$ELSE}
  420.                        if (heString = aKey) then begin
  421.                        {$ENDIF}
  422.                          aIndex := KeyHash;
  423.                          Result := true;
  424.                          Exit;
  425.                        end;
  426.                      end;
  427.       else
  428.         {bad news}
  429.         RaiseException('TaaHashTableLinear.htlFindPrim: invalid element state')
  430.       end;{case}
  431.     {we didn't find the key or an empty slot this time around, so
  432.      increment the index (taking care of the wraparound) and exit if
  433.      we've got back to the start again}
  434.     inc(KeyHash);
  435.     if (KeyHash = htlTableSize) then
  436.       KeyHash := 0;
  437.     if (KeyHash = FirstKeyHash) then begin
  438.       if (FirstDeleted <> -1) then
  439.         aIndex := FirstDeleted
  440.       else
  441.         aIndex := -1; {this value means that the table is full}
  442.       Exit;
  443.     end;
  444.     {$IFDEF DebugMode}
  445.     inc(htlDebugSeeks);
  446.     {$ENDIF}
  447.   end;{forever loop}
  448. end;
  449. {--------}
  450. function TaaHashTableLinear.htlGetItem(aIndex : integer) : pointer;
  451. begin
  452.   if (aIndex < 0) or (aIndex >= htlTableSize) then
  453.     RaiseException('TaaHashTableLinear.htlGetItem: index out of bounds');
  454.   with PHashElementArray(htlArray)^[aIndex] do
  455.     if (heState = hesInUse) then
  456.       Result := heObject
  457.     else
  458.       Result := nil;
  459. end;
  460. {--------}
  461. procedure TaaHashTableLinear.htlGrowTable;
  462. begin
  463.   {make the table roughly twice as large as before}
  464.   htlAlterTableSize(GetClosestPrime(succ(htlTableSize * 2)));
  465. end;
  466. {--------}
  467. function TaaHashTableLinear.htlHash(const aKey : string) : integer;
  468. begin
  469.   if Assigned(htlHashFunc) then
  470.     Result := htlHashFunc(aKey) mod htlTableSize
  471.   else
  472.     Result := 0;
  473. end;
  474. {--------}
  475. procedure TaaHashTableLinear.htlShrinkTable;
  476. begin
  477.   {make the table roughly half as large as before}
  478.   htlAlterTableSize(GetClosestPrime(pred(htlTableSize) div 2));
  479. end;
  480. {--------}
  481. procedure TaaHashTableLinear.Insert(const aKey : string; aObject : pointer);
  482. var
  483.   Inx : integer;
  484. begin
  485.   if htlFindPrim(aKey, Inx) then
  486.     RaiseException('TaaHashTableLinear.Insert: duplicate key');
  487.   if (Inx = -1) then
  488.     RaiseException('TaaHashTableLinear.Insert: table is full');
  489.   with PHashElementArray(htlArray)^[Inx] do begin
  490.     {$IFDEF Windows}
  491.     heString := NewStr(aKey);
  492.     {$ELSE}
  493.     heString := aKey;
  494.     {$ENDIF}
  495.     heObject := aObject;
  496.     heState := hesInUse;
  497.   end;
  498.   inc(htlCount);
  499.   {grow the table if we're over 2/3 full}
  500.   if ((htlCount * 3) > (htlTableSize * 2)) then
  501.     htlGrowTable;
  502. end;
  503. {====================================================================}
  504.  
  505. end.
  506.